/*
 * @(#)PageNavigatorTag.java $version 2010. 8. 11
 *
 * Copyright 2007-2010 NHN Corp. All rights Reserved. 
 * NHN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.nhncorp.usf.web.taglibs.pager;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.servlet.jsp.tagext.Tag;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.nhncorp.lucy.web.util.FreeMarkerUtil;
import com.nhncorp.usf.core.config.PagerConfigInfo;
import com.nhncorp.usf.core.config.PagerConfigInfos;
import com.nhncorp.usf.core.result.template.directive.PagerInfo;

/**
 * @author Web Platform Development Team
 * */
public class PageNavigatorTag extends BodyTagSupport {
	private static final long serialVersionUID = 1032805170226686033L;

	/** Paging */
	enum Paging {
		FIRST, PREV, INDEX, NEXT, LAST
	}

	Log log = LogFactory.getLog(PageNavigatorTag.class);

	public static final String DEFAULT_TEMPLATE = "defaultNavigator.ftl";
	public static final String DEFAULT_PAGE_PARAM = "page";
	public static final String PAGING_ELEMENT = "pagingElements";
	public static final String PAGE_INFO = "pagerInfo";
	public static final String PAGER_CONFIG = "config";
	public static final String REQUEST = "request";
	public static final String REQUEST_URI = "requestUri";
	public static final String QUERY_STRING = "queryString";
	public static final String TEMPLATE_NAME = "templateName";
	public static final String PAGE_PARAM = "pageParam";

	// Configuration 에서 지정한 Pager Type
	protected String type;
	protected String templateName;
	protected String pageParam;
	protected Map<String, Object> dataMap;

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	public String getTemplateName() {
		return templateName;
	}

	public void setTemplateName(String templateName) {
		this.templateName = templateName;
	}

	public String getPageParam() {
		return pageParam;
	}

	public void setPageParam(String pageParam) {
		this.pageParam = pageParam;
	}

	/**
	 * @throws JspException JspException
	 * @return Tag.EVAL_PAGE
	 * */
	public int doStartTag() throws JspException {
		if (log.isDebugEnabled()) {
			log.debug("type : " + type + ", template : " + templateName);
		}

		dataMap = new HashMap<String, Object>();
		HttpServletRequest request = (HttpServletRequest)pageContext.getRequest();
		PagerInfo pagerInfo = (PagerInfo)request.getAttribute(PAGE_INFO);
		Integer totalRow = (Integer) request.getAttribute("totalRow");
		
		if (totalRow == null) {
			totalRow = 0;
		}
		
		pagerInfo.init(type, totalRow, null, null);
		
		dataMap.put(PAGE_INFO, pagerInfo);
		dataMap.put(REQUEST, request);
		dataMap.put(PAGE_PARAM, pageParam == null ? DEFAULT_PAGE_PARAM : pageParam);

		String requestUri = pagerInfo.getRequestURI();
		dataMap.put(REQUEST_URI, requestUri.endsWith("?") ? requestUri.substring(0, requestUri.length() - 1)
			: requestUri);

		Object queryString = request.getAttribute(QUERY_STRING);

		if (queryString == null) {
			queryString = pagerInfo.getQueryString();
		}

		dataMap.put(QUERY_STRING, removePageParam((String)queryString));

		return Tag.EVAL_PAGE;
	}

	/**
	 * @throws JspException JspException
	 * @return Tag.EVAL_PAGE
	 * */
	@Override
	public int doEndTag() throws JspException {
		String configName = type == null ? "default" : type;
		PagerConfigInfo pagerConfigInfo = PagerConfigInfos.get(configName);

		if (isBlank(templateName)) {
			if (pagerConfigInfo == null) {
				throw new IllegalArgumentException("cannot resolve template name from configuration '" + configName
					+ "'");
			}

			templateName = pagerConfigInfo.getTemplate();
		}

		dataMap.put(PAGER_CONFIG, pagerConfigInfo);
		dataMap.put(TEMPLATE_NAME, templateName);

		String useTemplate = getEffectiveTemplateName();

		String contentStr = getBodyContent().getString();

		if (contentStr != null && contentStr.trim().length() != 0) {
			dataMap.put(PAGING_ELEMENT, getPagingElements(contentStr));
		} else {
			dataMap.put(PAGING_ELEMENT, getPagingElements("{first}{prev}{index}{next}{last}"));
		}

		StringBuffer sb = FreeMarkerUtil.processTemplate(useTemplate, dataMap);

		if (log.isDebugEnabled()) {
			log.debug("process Template(" + useTemplate + ")");
		}

		try {
			if (sb != null) {
				pageContext.getOut().write(sb.toString());
			} else {
				pageContext.getOut().write("");
			}
		} catch (IOException e) {
			log.error(e);
			throw new JspException(e);
		}
		return Tag.EVAL_PAGE;
	}

	/**
	 * 입력받은 정보를 통해 TemplateName을 정한다. fixed과 float는 미리 정의된 템플릿의 예약어로 사용.
	 * 
	 * @return templateName
	 */
	private String getEffectiveTemplateName() {
		if (isBlank(templateName) || templateName.equalsIgnoreCase("fixed") || templateName.equalsIgnoreCase("float")) {
			return DEFAULT_TEMPLATE;
		}

		return templateName;
	}

	/**
	 * @param value value
	 * @return boolean
	 * */
	private boolean isBlank(String value) {
		return value == null || value.length() == 0;
	}

	/**
	 * Pattern String을 파싱하여 출력하고자 하는 구성 요소를 정보화.
	 * 
	 * @param pattern	어떠한 구성요소를 포함 시킬지 나타내는 패턴 문자열	 
	 * @return	문자열을 파싱하여 생성된 {@link PagingElement} {@link List}
	 */
	private List<PagingElement> getPagingElements(String pattern) {

		List<PagingElement> pagingElements = new ArrayList<PagingElement>();

		PagingElement element = null;

		while (true) {
			int openPos = pattern.indexOf("{");
			int closePos = pattern.indexOf("}");

			if (openPos < 0 || closePos < 0) {
				break;
			}

			element = new PagingElement();
			element.setBefore(pattern.substring(0, openPos));
			parseElement(element, pattern.substring(openPos + 1, closePos));
			pagingElements.add(element);
			pattern = pattern.substring(closePos + 1);
		}
		if (element != null) {
			element.setAfter(pattern);
		}

		return pagingElements;
	}

	/**
	 * 입력된 elementType과 property 문자열을 파싱하여 {@link PagingElement} 인스턴스에 값을 할당.
	 * 문자열은 다음과 같은 포맷을 가져야 한다.
	 * index:seperator=* (elementType:property name=property value)
	 * 
	 * @param element	{@link PagingElement} 문자열을 파싱하여 값을 할당 할 대상
	 * @param patternStr	입력된 문자열.
	 */
	private void parseElement(PagingElement element, String patternStr) {

		int colon = patternStr.indexOf(":");

		if (colon < 0) {
			element.setElementType(patternStr);
			return;
		}

		element.setElementType(patternStr.substring(0, colon));
		patternStr = patternStr.substring(colon + 1);

		Map<String, String> properties = new HashMap<String, String>();
		int equalSign = patternStr.indexOf("=");
		int comma = patternStr.indexOf(",");

		while (equalSign >= 0) {
			String propertyStr = comma > -1 ? patternStr.substring(0, comma) : patternStr;
			String name = propertyStr.substring(0, equalSign);
			String value = comma < 0 ? propertyStr.substring(equalSign + 1) : propertyStr.substring(equalSign + 1,
				comma);
			properties.put(name.trim(), value.trim());
			
			if (comma < 0) {
				break;
			}
			
			patternStr = patternStr.substring(comma + 1);
			equalSign = patternStr.indexOf("=");
			comma = patternStr.indexOf(",");
		}
		
		element.setProperties(properties);
	}

	/**
	 * @param queryString queryString
	 * @return queryString
	 * */
	protected String removePageParam(String queryString) {
		if (queryString.startsWith("page=")) {
			queryString = queryString.replaceFirst("page=[0-9]*", "");
		} else if (queryString.indexOf("&page=") >= 0) {
			queryString = queryString.replaceFirst("&page=[0-9]*", "");
		}
		//        if (appendTotalRows && queryString.indexOf("&totalRows=") < 0
		//                && totalRows >= 0)
		//            queryString += "&totalRows=" + totalRows;
		return queryString;
	}
}
